package com.sina.mysql.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName DefaultStatementFactory
 * @Description:
 * @Author 段浩杰
 * @Date 2018/8/22 17:58
 * @Version 1.0
 */
public class DefaultStatementFactory implements StatementFactory, InitializingBean, DisposableBean {

    public static final String DEFAULT_RESOURCE_LOCATIONS = "classpath*:statement/**/*.xml";
    private static final Logger LOG = LoggerFactory.getLogger( DefaultStatementFactory.class );
    private Map<String, String> hqlMap;


    private Map<String, String> sqlMap;


    private String resourceLocations;

    private Pattern whitespacePattern;

    private Pattern textItemPattern;

    public DefaultStatementFactory() {

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        textItemPattern = Pattern.compile( "(?>(?>(?<!\\\\)')(?s).*?(?>(?<!\\\\)'))" );
        whitespacePattern = Pattern.compile( "\\s+" );

        hqlMap = new HashMap<>();
        sqlMap = new HashMap<>();
        // 初始化
        init();

        hqlMap = Collections.unmodifiableMap( hqlMap );
        sqlMap = Collections.unmodifiableMap( sqlMap );
        whitespacePattern = null;
        textItemPattern = null;
    }

    @Override
    public void destroy() throws Exception {
        if (hqlMap != null) {
            hqlMap.clear();
        }
        if (sqlMap != null) {
            sqlMap.clear();
        }
    }

    private void init() throws Exception {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        String locations = getResourceLocations() == null ? DEFAULT_RESOURCE_LOCATIONS : getResourceLocations();
        Resource[] resources = resolver.getResources( locations );
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        for (Resource resource : resources) {
            parseAndSaveStatement( builder.parse( resource.getInputStream() ) );
        }
    }


    private void parseAndSaveStatement(Document document) {
        Element root = document.getDocumentElement();
        String namespace = root.getAttribute( "namespace" );
        parseAndHoldStatement( root.getChildNodes(), namespace );

    }


    private void parseAndHoldStatement(NodeList nodeList, String namespace) {
        int len = nodeList.getLength();
        Element element;
        Node node;
        String key, value, oldValue;
        for (int i = 0; i < len; i++) {
            node = nodeList.item( i );
            if (!(node instanceof Element)) {
                continue;
            }
            element = (Element) node;
            key = namespace + "." + element.getAttribute( "name" );
            value = element.getTextContent();
            value = formatStatement( value );

            if ("hql".equals( element.getTagName() )) {
                oldValue = hqlMap.put( key, value );
            } else if ("sql".equals( element.getTagName() )) {
                oldValue = sqlMap.put( key, value );
            } else {
                throw new RuntimeException( "only support sql and hql tag" );
            }
            if (oldValue != null) {
                throw new RuntimeException( String.format( "%s[%s] conflict.", element.getTagName(), key ) );
            }
        }
    }

    @Override
    public String getHql(String qualifiedName) {
        return hqlMap.get( qualifiedName );
    }

    @Override
    public String getSql(String qualifiedName) {
        return sqlMap.get( qualifiedName );
    }

    /**
     * 格式化加载的 sql 和 hql ，即将所有非文本项('*')中的 空字符(\s)
     * 替换为一个空格,节省存储空间，减少网络传输.
     */
    protected String formatStatement(final String s) {
        Matcher textMatcher;
        textMatcher = textItemPattern.matcher( s );
        StringBuilder builder;
        builder = new StringBuilder( s.length() );
        int start = 0;
        while (textMatcher.find()) {
            builder.append(
                    whitespacePattern.matcher(
                            s.substring( start, textMatcher.start() )
                    ).replaceAll( " " )
            );
            builder.append( textMatcher.group() );
            start = textMatcher.end();
        }
        if (start < s.length()) {
            builder.append(
                    whitespacePattern.matcher(
                            s.substring( start ) )
                            .replaceAll( " " )
            );
        }
        return builder.toString();
    }


    public String getResourceLocations() {
        return resourceLocations;
    }

    public void setResourceLocations(String resourceLocations) {
        this.resourceLocations = resourceLocations;
    }
}

